/**
 * Ajax upload
 * Project page - http://valums.com/ajax-upload/
 * Copyright (c) 2008 Andris Valums, http://valums.com
 * Licensed under the MIT license (http://valums.com/mit-license/)
 * Version 3.6 (26.06.2009)
 */

/**
 * Changes from the previous version: 1. Fixed minor bug where click outside the
 * button would open the file browse window
 * 
 * For the full changelog please visit: http://valums.com/ajax-upload-changelog/
 */

(function() {

	var d = document, w = window;

	/**
	 * Get element by id
	 */
	function get(element) {
		if (typeof element == "string")
			element = d.getElementById(element);
		return element;
	}

	/**
	 * Attaches event to a dom element
	 */
	function addEvent(el, type, fn) {
		if (w.addEventListener) {
			el.addEventListener(type, fn, false);
		} else if (w.attachEvent) {
			var f = function() {
				fn.call(el, w.event);
			};
			el.attachEvent('on' + type, f)
		}
	}

	/**
	 * Creates and returns element from html chunk
	 */
	var toElement = function() {
		var div = d.createElement('div');
		return function(html) {
			div.innerHTML = html;
			var el = div.childNodes[0];
			div.removeChild(el);
			return el;
		}
	}();

	function hasClass(ele, cls) {
		return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
	}
	function addClass(ele, cls) {
		if (!hasClass(ele, cls))
			ele.className += " " + cls;
	}
	function removeClass(ele, cls) {
		var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
		ele.className = ele.className.replace(reg, ' ');
	}

	// getOffset function copied from jQuery lib (http://jquery.com/)
	if (document.documentElement["getBoundingClientRect"]) {
		// Get Offset using getBoundingClientRect
		// http://ejohn.org/blog/getboundingclientrect-is-awesome/
		var getOffset = function(el) {
			var box = el.getBoundingClientRect(), doc = el.ownerDocument, body = doc.body, docElem = doc.documentElement,

			// for ie
			clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft
					|| body.clientLeft || 0,

			// In Internet Explorer 7 getBoundingClientRect property is treated
			// as physical,
			// while others are logical. Make all logical, like in IE8.

			zoom = 1;
			if (body.getBoundingClientRect) {
				var bound = body.getBoundingClientRect();
				zoom = (bound.right - bound.left) / body.clientWidth;
			}
			if (zoom > 1) {
				clientTop = 0;
				clientLeft = 0;
			}
			var top = box.top
					/ zoom
					+ (window.pageYOffset || docElem && docElem.scrollTop
							/ zoom || body.scrollTop / zoom) - clientTop, left = box.left
					/ zoom
					+ (window.pageXOffset || docElem && docElem.scrollLeft
							/ zoom || body.scrollLeft / zoom) - clientLeft;

			return {
				top : top,
				left : left
			};
		}

	} else {
		// Get offset adding all offsets
		var getOffset = function(el) {
			if (w.jQuery) {
				return jQuery(el).offset();
			}

			var top = 0, left = 0;
			do {
				top += el.offsetTop || 0;
				left += el.offsetLeft || 0;
			} while (el = el.offsetParent);

			return {
				left : left,
				top : top
			};
		}
	}

	function getBox(el) {
		var left, right, top, bottom;
		var offset = getOffset(el);
		left = offset.left;
		top = offset.top;

		right = left + el.offsetWidth;
		bottom = top + el.offsetHeight;

		return {
			left : left,
			right : right,
			top : top,
			bottom : bottom
		};
	}

	/**
	 * Crossbrowser mouse coordinates
	 */
	function getMouseCoords(e) {
		// pageX/Y is not supported in IE
		// http://www.quirksmode.org/dom/w3c_cssom.html
		if (!e.pageX && e.clientX) {
			// In Internet Explorer 7 some properties (mouse coordinates) are
			// treated as physical,
			// while others are logical (offset).
			var zoom = 1;
			var body = document.body;

			if (body.getBoundingClientRect) {
				var bound = body.getBoundingClientRect();
				zoom = (bound.right - bound.left) / body.clientWidth;
			}

			return {
				x : e.clientX / zoom + d.body.scrollLeft
						+ d.documentElement.scrollLeft,
				y : e.clientY / zoom + d.body.scrollTop
						+ d.documentElement.scrollTop
			};
		}

		return {
			x : e.pageX,
			y : e.pageY
		};

	}
	/**
	 * Function generates unique id
	 */
	var getUID = function() {
		var id = 0;
		return function() {
			return 'ValumsAjaxUpload' + id++;
		}
	}();

	function fileFromPath(file) {
		return file.replace(/.*(\/|\\)/, "");
	}

	function getExt(file) {
		return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
	}

	// Please use AjaxUpload , Ajax_upload will be removed in the next version
	Ajax_upload = AjaxUpload = function(button, options) {
		if (button.jquery) {
			// jquery object was passed
			button = button[0];
		} else if (typeof button == "string" && /^#.*/.test(button)) {
			button = button.slice(1);
		}
		button = get(button);

		this._input = null;
		this._button = button;
		this._disabled = false;
		this._submitting = false;
		// Variable changes to true if the button was clicked
		// 3 seconds ago (requred to fix Safari on Mac error)
		this._justClicked = false;
		this._parentDialog = d.body;

		if (window.jQuery && jQuery.ui && jQuery.ui.dialog) {
			var parentDialog = jQuery(this._button).parents('.ui-dialog');
			if (parentDialog.length) {
				this._parentDialog = parentDialog[0];
			}
		}

		this._settings = {
			// Location of the server-side upload script
			action : 'upload.php',
			// File upload name
			name : 'userfile',
			// Additional data to send
			data : {},
			// Submit file as soon as it's selected
			autoSubmit : true,
			// The type of data that you're expecting back from the server.
			// Html and xml are detected automatically.
			// Only useful when you are using json data as a response.
			// Set to "json" in that case.
			responseType : false,
			// When user selects a file, useful with autoSubmit disabled
			onChange : function(file, extension) {
			},
			// Callback to fire before file is uploaded
			// You can return false to cancel upload
			onSubmit : function(file, extension) {
			},
			// Fired when file upload is completed
			// WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
			onComplete : function(file, response) {
			}
		};

		// Merge the users options with our defaults
		for ( var i in options) {
			this._settings[i] = options[i];
		}

		this._createInput();
		this._rerouteClicks();
	}

	// assigning methods to our class
	AjaxUpload.prototype = {
		setData : function(data) {
			this._settings.data = data;
		},
		disable : function() {
			this._disabled = true;
		},
		enable : function() {
			this._disabled = false;
		},
		// removes ajaxupload
		destroy : function() {
			if (this._input) {
				if (this._input.parentNode) {
					this._input.parentNode.removeChild(this._input);
				}
				this._input = null;
			}
		},
		/**
		 * Creates invisible file input above the button
		 */
		_createInput : function() {
			var self = this;
			var input = d.createElement("input");
			input.setAttribute('type', 'file');
			input.setAttribute('name', this._settings.name);
			var styles = {
				'position' : 'absolute',
				'margin' : '-5px 0 0 -175px',
				'padding' : 0,
				'width' : '220px',
				'height' : '30px',
				'fontSize' : '14px',
				'opacity' : 0,
				'cursor' : 'pointer',
				'display' : 'none',
				'zIndex' : 2147483583
			// Max zIndex supported by Opera 9.0-9.2x
			// Strange, I expected 2147483647
			};
			for ( var i in styles) {
				input.style[i] = styles[i];
			}

			// Make sure that element opacity exists
			// (IE uses filter instead)
			if (!(input.style.opacity === "0")) {
				input.style.filter = "alpha(opacity=0)";
			}

			this._parentDialog.appendChild(input);

			addEvent(input, 'change',
					function() {
						// get filename from input
						var file = fileFromPath(this.value);
						// 自定义, 将得到的文件名赋值在选择后复制到自己的文本框中, 不用等到提交时才能看到
						// $('#picfileTitle').val(file);
						if (self._settings.onChange.call(self, file,
								getExt(file)) == false) {
							return;
						}
						// Submit form when value is changed
						if (self._settings.autoSubmit) {
							self.submit();
						}
					});

			// Fixing problem with Safari
			// The problem is that if you leave input before the file select
			// dialog opens
			// it does not upload the file.
			// As dialog opens slowly (it is a sheet dialog which takes some
			// time to open)
			// there is some time while you can leave the button.
			// So we should not change display to none immediately
			addEvent(input, 'click', function() {
				self.justClicked = true;
				setTimeout(function() {
					// we will wait 3 seconds for dialog to open
					self.justClicked = false;
				}, 2500);
			});

			this._input = input;
		},
		_rerouteClicks : function() {
			var self = this;

			// IE displays 'access denied' error when using this method
			// other browsers just ignore click()
			// addEvent(this._button, 'click', function(e){
			// self._input.click();
			// });

			var box, dialogOffset = {
				top : 0,
				left : 0
			}, over = false;

			addEvent(self._button, 'mouseover', function(e) {
				if (!self._input || over)
					return;

				over = true;
				box = getBox(self._button);

				if (self._parentDialog != d.body) {
					dialogOffset = getOffset(self._parentDialog);
				}
			});

			// We can't use mouseout on the button,
			// because invisible input is over it
			addEvent(document, 'mousemove', function(e) {
				var input = self._input;
				if (!input || !over)
					return;

				if (self._disabled) {
					removeClass(self._button, 'hover');
					input.style.display = 'none';
					return;
				}

				var c = getMouseCoords(e);
				
				if (((c.x+1) >= box.left) && (c.x <= box.right) && (c.y >= box.top)
						&& (c.y <= box.bottom)) {

					input.style.top = c.y - dialogOffset.top + 'px';
					input.style.left = c.x - dialogOffset.left + 'px';
					input.style.display = 'block';
					addClass(self._button, 'hover');

				} else {
					// mouse left the button
					over = false;

					var check = setInterval(function() {
						// if input was just clicked do not hide it
						// to prevent safari bug

						if (self.justClicked) {
							return;
						}

						if (!over) {
							input.style.display = 'none';
						}

						clearInterval(check);

					}, 25);

					removeClass(self._button, 'hover');
				}
			});

		},
		/**
		 * Creates iframe with unique name
		 */
		_createIframe : function() {
			// unique name
			// We cannot use getTime, because it sometimes return
			// same value in safari :(
			var id = getUID();

			// Remove ie6 "This page contains both secure and nonsecure items"
			// prompt
			// http://tinyurl.com/77w9wh
			var iframe = toElement('<iframe src="javascript:false;" name="'
					+ id + '" />');
			iframe.id = id;
			iframe.style.display = 'none';
			d.body.appendChild(iframe);
			return iframe;
		},
		/**
		 * Upload file without refreshing the page
		 */
		submit : function() {
			var self = this, settings = this._settings;

			if (this._input.value === '') {
				// there is no file
				return;
			}

			// get filename from input
			var file = fileFromPath(this._input.value);

			// execute user event
			if (!(settings.onSubmit.call(this, file, getExt(file)) == false)) {
				// Create new iframe for this submission
				var iframe = this._createIframe();

				// Do not submit if user function returns false
				var form = this._createForm(iframe);
				form.appendChild(this._input);

				form.submit();

				d.body.removeChild(form);
				form = null;
				this._input = null;

				// create new input
				this._createInput();

				var toDeleteFlag = false;

				addEvent(
						iframe,
						'load',
						function(e) {

							if (// For Safari
							iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';"
									||
									// For FF, IE
									iframe.src == "javascript:'<html></html>';") {

								// First time around, do not delete.
								if (toDeleteFlag) {
									// Fix busy state in FF3
									setTimeout(function() {
										d.body.removeChild(iframe);
									}, 0);
								}
								return;
							}

							var doc = iframe.contentDocument ? iframe.contentDocument
									: frames[iframe.id].document;

							// fixing Opera 9.26
							if (doc.readyState && doc.readyState != 'complete') {
								// Opera fires load event multiple times
								// Even when the DOM is not ready yet
								// this fix should not affect other browsers
								return;
							}

							// fixing Opera 9.64
							if (doc.body && doc.body.innerHTML == "false") {
								// In Opera 9.64 event was fired second time
								// when body.innerHTML changed from false
								// to server response approx. after 1 sec
								return;
							}

							var response;
							if (doc.XMLDocument) {
								// response is a xml document IE property
								response = doc.XMLDocument;
							} else if (doc.body) {
								// response is html document or plain text
								response = doc.body.innerHTML;
								if (settings.responseType
										&& settings.responseType.toLowerCase() == 'json') {
									// If the document was sent as
									// 'application/javascript' or
									// 'text/javascript', then the browser wraps
									// the text in a <pre>
									// tag and performs html encoding on the
									// contents. In this case,
									// we need to pull the original text content
									// from the text node's
									// nodeValue property to retrieve the
									// unmangled content.
									// Note that IE6 only understands text/html
									if (doc.body.firstChild
											&& doc.body.firstChild.nodeName
													.toUpperCase() == 'PRE') {
										response = doc.body.firstChild.firstChild.nodeValue;
									}
									if (response) {
										response = window["eval"]("("
												+ response + ")");
									} else {
										response = {};
									}
								}
							} else {
								// response is a xml document
								var response = doc;
							}

							settings.onComplete.call(self, file, response);

							// Reload blank page, so that reloading main page
							// does not re-submit the post. Also, remember to
							// delete the frame
							toDeleteFlag = true;

							// Fix IE mixed content issue
							iframe.src = "javascript:'<html></html>';";
						});

			} else {
				// clear input to allow user to select same file
				// Doesn't work in IE6
				// this._input.value = '';
				d.body.removeChild(this._input);
				this._input = null;

				// create new input
				this._createInput();
			}
		},
		/**
		 * Creates form, that will be submitted to iframe
		 */
		_createForm : function(iframe) {
			var settings = this._settings;

			// method, enctype must be specified here
			// because changing this attr on the fly is not allowed in IE 6/7
			var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
			form.style.display = 'none';
			form.action = settings.action;
			form.target = iframe.name;
			d.body.appendChild(form);

			// Create hidden input element for each data key
			for ( var prop in settings.data) {
				var el = d.createElement("input");
				el.type = 'hidden';
				el.name = prop;
				el.value = settings.data[prop];
				form.appendChild(el);
			}
			return form;
		}
	};
})();